Skip to content

fix(cli): classify user-input mistakes instead of reporting unknown-error - #4010

Merged
kojiwakayama merged 11 commits into
mainfrom
fix/dx-usage-errors
Aug 23, 2026
Merged

kojiwakayama merged 11 commits into
mainfrom
fix/dx-usage-errors

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses veryfront/veryfront-issue-inbox#740 and fixes all seven repros it lists.

That issue stays open on purpose. 31 sites across 8 files still render [unknown-error] for
the same class of user-input mistake (verified live: veryfront uploads list --limit abc).
They are tracked in veryfront/veryfront-issue-inbox#767, together with the cli/router.ts:291
prefix heuristic they currently depend on for their exit codes.

#4002 has landed, so this now targets main directly. origin/main was merged in, not rebased.

User-input mistakes rendered as [unknown-error] Unknown/unclassified error … Check logs for more details because the sites threw plain Errors or createError({ type: "config" }), which carry no registry slug; exit codes were split 1/2 by a message-prefix heuristic in the router. Two registered errors now cover them:

Slug Exit Used for
invalid-argument (existing) 2 anything the caller typed wrong: parseArgsOrThrow (so every command's arg-parse failure, e.g. dev --port abc), parseRuntime, project-name validation, invalid integrations, generate usage
already-exists (new, 409) 1 writing over something that is there: init into a dir holding scaffold files (incl. #4002's "file or link in the way" refusal), generate onto an existing file

Before / after (0.1.1251 vs this branch):

$ veryfront init app --runtime rust
✗ [unknown-error] Unknown/unclassified error      →  ✗ [invalid-argument] Invalid argument
  Suggestion: Check logs for more details              Detail: Invalid runtime value: "rust". Must be one of: node, bun, deno.
  exit 2                                                exit 2
$ veryfront init taken            (taken/README.md exists)
✗ [unknown-error] …  exit 1                        →  ✗ [already-exists] Target already exists … exit 1
$ veryfront generate tool calc    (exists)
✗ [unknown-error] …  exit 1                        →  ✗ [already-exists] Target already exists … exit 1
$ veryfront init nested/x                          →  [invalid-argument], exit 2 (was exit 1)

Decisions:

  • invalid-argument is reused rather than adding a CLI-only slug: login already returns registrySlug: "invalid-argument" for usage errors and the docs catalog already describes it as "Command received invalid argument". Only its registry title changes ("Invalid function argument" → "Invalid argument"; nothing asserted the old title).
  • already-exists gets a command-agnostic suggestion ("choose a different name, or remove the existing target first") because generate has no --force; init's detail keeps "Use --force to overwrite."
  • Exit 2 = the style guide's "invalid usage" code. The router's startsWith("Invalid ") fallback is left in place for sites not yet converted.

Test plan

  • RED first: 8 new tests failed on the base (registry lookups, parseRuntime, parseArgsOrThrow, generate usage + conflict, createProject name + conflict, and the subprocess-level init integration pinning exit 2 + [invalid-argument] / exit 1 + [already-exists] / never unknown-error)
  • GREEN: src/errors/, cli/commands/init/ (incl. all integration files), cli/commands/generate/, cli/shared/, cli/app/operations/, cli/commands/{deploy,merge,dev}/handler.test.ts, cli/auth/exit-code.integration.test.ts, tests/docs/error-docs-links.test.ts — 60+ files pass
  • Pass 2 against the real CLI from a sandbox outside the repo: all seven repros from fix: apply code quality findings #740 render the expected slug and exit code
  • Ratchets: registry count 120→121, GENERAL 13→14, general catalog 6→7; lint:test-typecheck, lint:anti-slop, lint:sanitizer-baseline, docs:errors:check, docs:public:check, docs:api-reference:check (regenerated with CI's Deno 2.7.7) all clean
  • src/server/services/rsc/endpoints/rsc-bundles.generated.ts regenerated: the RSC client bundles inline the error registry, so the renamed invalid-argument title and the new already-exists entry made the committed bundle stale and failed generate:manifests:check, which deno task typecheck runs first

kojiwakayama and others added 4 commits August 23, 2026 00:25
…be overwritten

`veryfront init app` refused any existing `app/`, including an empty one or a
fresh clone holding only `.git`, with "Directory already exists". Every
mainstream scaffolder accepts those, and `mkdir app && veryfront init app` is
the first thing many developers type.

A conflict is now a file the scaffold would write over, not the directory
existing. `createProject` is the single authority: the named path uses the
same `findExistingPaths` check the current-directory path already used, and
both directory-existence checks in `initCommand` are gone. The refusal names
the files and points at `--force`:

  Directory "app" already contains README.md. Use --force to overwrite.

`.gitignore` is merged rather than replaced, so it never conflicts. The
interactive wizard now runs before a refusal for a taken name; the message it
ends on says exactly which files are in the way.

The `vf_create_project` MCP tool keeps its own directory check and message;
aligning it is a separate change.

Tests: empty directory and unrelated-file cases at the `createProject`,
`initCommand`, and subprocess levels; the conflict message for a named
directory; existing expectations updated from "already exists" to the
file-level message. API reference pins regenerated with CI's Deno.
Accepting an existing target directory means the scaffold now meets states
the old "directory already exists" check never let it reach. One of them
wrote outside the project.

`findExistingPaths` asks whether `app/page.tsx` exists. When `app` is a
regular file, that path cannot resolve, so the check reports no conflict.
`writeScaffoldFiles` then writes the root files (`README.md`, `AGENTS.md`)
and fails on `ensureDir("app")` with a raw stat error, leaving a half
scaffold behind. When `app` is a link to another directory, nothing fails
at all: `veryfront init app` exits 0, prints "app ready", and leaves
`page.tsx`, `layout.tsx` and `about/page.mdx` in the link target instead of
the project you named.

`createProject` now checks every directory the scaffold has to create,
before it writes anything, and refuses when one is already a file or a
link:

  Directory "app" already contains app as a file or a link, and the
  scaffold needs a directory there. Move it aside or use a different name.

The check runs whatever the conflict policy is. `--force` says you accept
your own files being replaced, not the scaffold writing somewhere else.

Every segment is checked, not just the first, so a real `app/` with a file
at `app/about` is caught before `app/page.tsx` is written. A real directory
that is already there is never blocked: it is exactly what the scaffold is
about to create.

The current-directory path had the same hole, so `cd repo && veryfront
init` with a linked `app/` wrote outside the repo too. The check covers
both paths because it sits in `createProject`.

Tests: a file and a link at a scaffold directory, for the named path, the
current-directory path, and under `--force`, plus a block one level down at
`app/about`, each asserting nothing was written through or beside it; and an
existing real `app/` that must still scaffold. Every refusal test fails
without the check.
…h directories

Two more places where accepting an existing directory let a write land
somewhere it was never asked to go.

A link at the scaffold path itself escaped the preflight, which only walked
the directories above it. `findExistingPaths` resolves a dangling link to
nothing and reports it absent, so `proj/README.md -> ../outside.md` made
`veryfront init proj` exit 0, print "proj ready", and write the README to
`outside.md` outside the project. The check now walks every segment,
including the last, and refuses a link anywhere along the path:

  Directory "proj" already contains README.md as a file or a link the
  scaffold cannot write through. Move it aside or use a different name.

A real file at a scaffold path is deliberately not refused here. It resolves
fine and stays the ordinary conflict pointing at `--force`, pinned by a test
so this cannot drift into refusing any directory with a file in it.

The named target being a link is still allowed on purpose. `ln -s
/mnt/big/app app && veryfront init app` puts the project on another volume
and every file is reachable at the path you named. Only a link you did not
name can surprise you.

The TUI is the second caller of `createProject` with a fail policy, and it
relied on the directory check this branch removed. It reserves a new remote
slug, then scaffolds into `projects/<slug>`, then writes the link for that
slug. With the check gone it would adopt an existing `projects/<slug>` that
holds none of the template files, and repoint a directory that is already
another project. It now refuses before scaffolding. The constraint belongs
in that caller, not in `createProject`: `veryfront init` accepting a
directory that exists is the point of this branch, and the TUI wanting a
fresh one is the opposite requirement.

Tests: a dangling link at a scaffold path, the same under `--force`, a real
file at a scaffold path that must stay an overwritable conflict, and a TUI
slug whose directory already exists and is linked elsewhere. All fail
without these changes.
…rror

A bad flag, an invalid project name, a missing positional, or a target that
already exists all rendered as "[unknown-error] Unknown/unclassified error"
with "Check logs for more details", because those sites threw plain Errors or
`createError({ type: "config" })`, which carry no registry slug. Exit codes
were split between 1 and 2 by a message-prefix heuristic.

Two registered errors now cover them:

- `invalid-argument` (existing, exit 2) for anything the caller typed wrong.
  Its title drops "function" - it has always been the CLI's usage error too
  (`login` already used it) and the docs catalog describes it that way.
  `parseArgsOrThrow` now throws it, which classifies every command's
  argument-parse failure at once (`dev --port abc`, ...), as do
  `parseRuntime`, project-name validation, invalid integrations, and the
  `generate` usage error.
- `already-exists` (new, 409, exit 1) for writing over something that is
  there: `init` into a directory holding scaffold files, and `generate` onto
  an existing file. Its suggestion is command-agnostic because `generate` has
  no --force; `init` keeps the --force hint in its detail.

Exit code 2 is the style guide's "invalid usage" code; the router heuristic
stays as the fallback for sites not yet converted.

Tests pin the slug and exit code at the unit level (registry, parseRuntime,
parseArgsOrThrow, generate, createProject) and at the process level (init
integration: exit 2 with [invalid-argument], exit 1 with [already-exists],
never unknown-error). Error reference and API reference regenerated.

Closes veryfront/veryfront-issue-inbox#740.
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bd9da831-0bf0-4ccd-ae9d-12de24063890

📥 Commits

Reviewing files that changed from the base of the PR and between bd5d033 and dd86e75.

⛔ Files ignored due to path filters (1)
  • src/server/services/rsc/endpoints/rsc-bundles.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (28)
  • cli/commands/generate/command.ts
  • cli/commands/generate/handler.test.ts
  • cli/commands/generate/handler.ts
  • cli/commands/init/init-command.ts
  • cli/commands/init/init.integration.test.ts
  • cli/commands/init/runtime.test.ts
  • cli/commands/init/runtime.ts
  • cli/mcp/tools/catalog-tools.test.ts
  • cli/mcp/tools/catalog-tools.ts
  • cli/router.test.ts
  • cli/shared/args.test.ts
  • cli/shared/args.ts
  • cli/shared/project-creation.test.ts
  • cli/shared/project-creation.ts
  • docs/api-reference/veryfront/errors.md
  • docs/api-reference/veryfront/index.client.md
  • docs/api-reference/veryfront/index.md
  • docs/api-reference/veryfront/scaffold.md
  • docs/api-reference/veryfront/security.md
  • docs/guides/errors.md
  • src/errors/catalog/general-errors.test.ts
  • src/errors/catalog/general-errors.ts
  • src/errors/error-registry.test.ts
  • src/errors/error-registry/general.ts
  • src/errors/index.ts
  • src/server/handlers/dev/dashboard/api.test.ts
  • tests/integration/cli/commands/generate/generate-conflict.test.ts
  • tests/integration/cli/mcp/tools/catalog-tools-project-creation.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… titles

The RSC client bundles inline the error registry, so renaming
`invalid-argument` and adding `already-exists` left
`rsc-bundles.generated.ts` holding the old "Invalid function argument"
title. `deno task typecheck` runs `generate:manifests:check` first and
failed on it, which the required `ci (typecheck)` job would have caught
once this PR targets main.
Base automatically changed from fix/dx-init-empty-dir to main August 23, 2026 06:54
# Conflicts:
#	cli/commands/init/init-command.ts
#	cli/commands/init/init.integration.test.ts
#	cli/shared/project-creation.test.ts
#	cli/shared/project-creation.ts
#	docs/api-reference/veryfront/scaffold.md
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown

📦 Client bundle boundary

Entrypoint Modules Source size Server leaks
src/index.client.ts 327 1963 KiB ✅ 0

A server module in a client graph aborts hydration in the browser. New leaks fail CI; known leaks are tracked in scripts/lint/client-bundle-baseline.json to burn down.

@kwakayama
kwakayama added this pull request to the merge queue Aug 23, 2026
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: daaaa0749c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 23, 2026
@kwakayama
kwakayama added this pull request to the merge queue Aug 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 23, 2026
The registry count assertion is an exact number, so two PRs each adding one
error are individually correct and wrong together. This branch adds
already-exists and main added one more while it waited, giving 122.

Caught by the merge queue rather than by either branch's own CI: each passed
alone.
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

@codex review

@kojiwakayama

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: a18f8b59f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 23, 2026
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 23, 2026
…fuses (#4011)

* fix(mcp): let vf_create_project refuse exactly what veryfront init refuses

The tool kept its own pre-check, "Directory already exists: <path>", so an
empty directory or a fresh clone holding only .git was refused here while
`veryfront init` (since the current-directory and empty-directory fixes)
scaffolds into both, and a real conflict was reported without naming the
file. The pre-check is gone: `createProject` is the single authority, and its
refusal - `Directory "x" already contains README.md. Use --force to
overwrite.` - reaches the caller through the existing failure envelope.

Tests: a directory holding a scaffold file is refused with the file named and
left intact; an existing empty directory scaffolds.

* fix(init): refuse a linked project root instead of scaffolding through it

Dropping the `vf_create_project` pre-check handed the target decision to
`createProject`, which never looked at the project root itself:
`findUnwritablePaths` walks only the paths beneath it. A symlink at the
root therefore passed, and the scaffold wrote its files, its
`.gitignore` and its installed dependencies into the link target, which
can sit outside the requested parent entirely. The tool reported
success.

The scaffold picks that path itself by joining the name onto the parent,
so a link there sends every write somewhere the caller never named.
`createProject` now refuses it, for the same reason a link at any other
scaffold path is already refused. A parent directory the caller passed
in is their own choice, so only the derived path is checked.

Fixing it in `createProject` closes the same hole for `veryfront init`,
not just the MCP tool.

* Refuse linked gitignore before scaffold merge

The project creation preflight already rejects symlinks on paths the scaffold writes outright. The generated .gitignore is merged instead of treated as a normal overwrite conflict, but the merge still writes to that path and would follow a symlink outside the project.

This keeps regular .gitignore merge behavior while adding it to the write-through protection list, with shared and MCP regression coverage for the outside-target case.

Constraint: Preserve existing .gitignore merge behavior for regular files

Rejected: Add .gitignore to scaffoldWritePaths | that would turn normal .gitignore merges into overwrite conflicts

Confidence: high

Scope-risk: narrow

Tested: VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

* Protect scaffold reuse lockfile leaves

The project creation preflight now treats installer-generated lockfiles as possible writes when dependency installation is enabled, so fail-policy reuse rejects a user-owned package-lock before npm can replace it.

The same protected-leaf check rejects a directory at merge-only leaves such as .gitignore before scaffold files are written, preventing partial project creation.

Constraint: Preserve regular .gitignore merge behavior and force-overwrite behavior for ordinary lockfiles

Rejected: Disable dependency installation for reused directories | too broad and would remove expected vf_create_project behavior

Confidence: high

Scope-risk: narrow

Tested: VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

* Close remaining scaffold reuse write-through gaps

The reuse preflight now covers npm's hidden lockfile and rejects non-file protected merge leaves before scaffold writes begin. The generated scaffold docs were refreshed so source anchors point at the current declarations.

Constraint: Preserve regular .gitignore merge behavior and ordinary lockfile fail-policy semantics.

Rejected: Reject any existing node_modules directory | too broad because only npm's hidden lockfile is a deterministic installer write target here.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep merge-only leaves in protectedLeafPaths out of normal overwrite conflict detection, but preflight every non-regular leaf before writeGitignore runs.

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno fmt --check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts docs/api-reference/veryfront/scaffold.md

Tested: Deno 2.7.7; deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno task docs:api-reference:check

Not-tested: Full repository test suite.

* Protect npm shrinkwrap during scaffold reuse

npm treats npm-shrinkwrap.json as an installation-owned lockfile and can update it during install. Reused project directories now preflight that path with the rest of the installer write set so conflictPolicy fail refuses it before scaffold writes or dependency installation.

Constraint: Keep dependency installation enabled for safe reused directories.

Rejected: Disable npm install whenever a reused directory exists | too broad; only deterministic installer-owned write targets need preflight protection.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Add future package-manager-owned write targets to installerWritePaths so conflict detection and write-through protection stay coupled.

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno fmt --check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts docs/api-reference/veryfront/scaffold.md

Tested: Deno 2.7.7; deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno task docs:api-reference:check

Not-tested: Full repository test suite.

* Refuse npm node_modules reuse before install

npm install can prune existing node_modules content before returning success. Reused project directories now treat node_modules as an npm installer conflict when dependency installation is enabled, so conflictPolicy fail refuses the directory before scaffold writes or install side effects.

Constraint: Preserve safe reused-directory scaffolding when dependency installation has no existing npm-owned tree to mutate.

Rejected: Treat node_modules as a protected write-through leaf | conflict detection gives the user-facing fail-policy error while existing symlink protection still comes from node_modules/.package-lock.json path traversal.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep installer conflict paths separate from installer file write paths when the path is a directory-level npm side effect.

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno fmt --check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts docs/api-reference/veryfront/scaffold.md

Tested: Deno 2.7.7; deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno task docs:api-reference:check

Not-tested: Full repository test suite.

* Restore project creation style invariants

The scaffold creation module now keeps veryfront package imports with the other external imports and keeps the unwritable-paths documentation directly attached to the function it describes.

Constraint: Address exact-head standards review without changing runtime behavior.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: Deno 2.7.7; deno fmt --check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts docs/api-reference/veryfront/scaffold.md

Tested: Deno 2.7.7; deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno task docs:api-reference:check

Not-tested: Full repository test suite.

* Replace gitignore atomically before scaffolding

Existing .gitignore files are merge-only, but direct writes can mutate hard-linked files and late write failures can leave a partial scaffold. The merge now writes a same-directory temporary file, renames it over .gitignore, and happens before the rest of the scaffold output.

Constraint: Preserve regular .gitignore merge semantics while preventing writes through shared inodes or late permission failures.

Rejected: Keep direct writeTextFile with more preflight checks | hard links are easier and safer to handle by replacing the path instead of mutating the inode.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep .gitignore as a merge-only path; do not re-add it to overwrite conflict detection without preserving existing ignore entries.

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: Deno 2.7.7; VF_DISABLE_LRU_INTERVAL=1 deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno fmt --check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts docs/api-reference/veryfront/scaffold.md

Tested: Deno 2.7.7; deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: Deno 2.7.7; deno task docs:api-reference:check

Not-tested: Full repository test suite.

* Fail closed on unreadable gitignore merges

Existing .gitignore content is merge input. Treating every read failure as absence could replace unreadable user content and then continue scaffolding. The merge now treats only missing .gitignore as absent; any other read failure happens before scaffold writes.

Constraint: Preserve absent .gitignore behavior and atomic replacement semantics.

Rejected: Swallow all read errors and rely on rename | replaces unreadable existing content under fail policy.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Do not broaden read-error handling for merge-only files; only NotFound means absent.

Tested: deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: deno fmt --check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts docs/api-reference/veryfront/scaffold.md

Tested: deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: deno task docs:api-reference:check

Not-tested: Full repository test suite.

* Protect Bun scaffold installs from existing node_modules

Bun installs dependencies into node_modules like npm-family package managers. Reused project targets must therefore reject an existing node_modules tree before installation can prune or replace user-owned files. The unsupported atomic-gitignore capability branch now also uses the file-local config error helper so the error stays on the registered VeryfrontError path.

Constraint: Keep MCP create-project behavior unchanged; it currently exposes no runtime input and always calls shared creation with runtime node.

Rejected: Add a Bun runtime option to vf_create_project | broadens the MCP tool contract beyond this conflict-safety fix.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep installer conflict paths aligned with NPM_FAMILY_CLIENTS when a package manager writes node_modules.

Tested: deno test --no-check --allow-all cli/shared/project-creation.test.ts

Tested: deno test --no-check --allow-all cli/mcp/tools/catalog-tools.test.ts

Tested: deno fmt --check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts docs/api-reference/veryfront/scaffold.md

Tested: deno lint cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: deno check cli/shared/project-creation.ts cli/shared/project-creation.test.ts cli/mcp/tools/catalog-tools.test.ts

Tested: deno task docs:api-reference:check

Not-tested: Full repository test suite.

---------

Co-authored-by: Kentaro Wakayama <kentaro@codersociety.com>
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

@codex review exact head 5437181. This head is the merge of previously clean/reviewed #4010 head a18f8b5 and exact-reviewed #4011 head b09329a. Please check their combined behavior and report any blocker on this exact commit.

@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cli/shared/project-creation.ts 91.83% 8 Missing ⚠️
cli/commands/generate/command.ts 20.00% 4 Missing ⚠️
cli/commands/generate/handler.ts 57.14% 3 Missing ⚠️
cli/commands/init/init-command.ts 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 5437181b41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kojiwakayama

Copy link
Copy Markdown
Contributor Author

Pushed exact head dd86e75bfb5e6f2771011c0b7e29b8af29cfe04f.

This fixes the only red hosted gate after merging current main:

  • moved the generate overwrite regression to tests/integration/cli/commands/generate/
  • moved the MCP project-creation filesystem/symlink/lockfile scenarios to tests/integration/cli/mcp/tools/
  • removed the superseded unit assertion that any existing directory must fail; the new behavior intentionally permits an empty directory and integration coverage pins it
  • did not grow the shrink-only semantic migration inventory

Fresh exact-head verification: 44 focused steps passed; semantic unit-boundary audit passed; test-layout passed; production typecheck passed; full deno task lint:ci passed; generated docs checks and git diff --check passed.

@codex review

Please review exact head dd86e75bfb5e6f2771011c0b7e29b8af29cfe04f. There are no unresolved review threads.

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown

Greptile Summary

The PR classifies several CLI input and target-conflict failures with registered errors while preserving their intended exit-code distinction.

  • Adds and exports the already-exists error and broadens invalid-argument usage.
  • Converts argument parsing, runtime validation, project-name validation, integration validation, and generate conflicts to structured errors.
  • Strengthens project-creation conflict checks for scaffold files, installer artifacts, symlinks, and .gitignore replacement.
  • Updates generated error documentation, bundled registry data, and unit/integration coverage.

Confidence Score: 5/5

The PR appears safe to merge; no concrete blocking or independently actionable non-blocking issue remains.

The changed error classifications preserve the intended usage and operational exit codes, while the expanded project-creation preflight rejects reachable conflicts and symlink write-through paths before writing.

Important Files Changed

Filename Overview
cli/shared/project-creation.ts Centralizes classified validation and conflict failures, expands installer and symlink preflight checks, and atomically replaces the merged .gitignore.
cli/shared/args.ts Changes shared parser failures from plain errors to registered invalid-argument errors with usage exit code 2.
cli/commands/generate/command.ts Reports scaffold file conflicts through the new already-exists definition while retaining conflict details.
cli/commands/generate/handler.ts Classifies missing or invalid generate operands as registered usage errors.
cli/commands/init/runtime.ts Classifies unsupported runtime values as invalid-argument and records diagnostic context.
cli/mcp/tools/catalog-tools.ts Removes the coarse existing-directory rejection and delegates precise conflict detection to shared project creation.
src/errors/error-registry/general.ts Adds already-exists, assigns its conflict semantics and exit code, and generalizes the invalid-argument title.
src/errors/catalog/general-errors.ts Adds user-facing catalog guidance for the new already-exists slug.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[CLI or MCP project request] --> B[Validate arguments and project name]
  B -->|Invalid input| C[invalid-argument, exit 2]
  B -->|Valid| D[Assemble scaffold]
  D --> E[Preflight scaffold, installer, and symlink conflicts]
  E -->|Conflict| F[already-exists, exit 1]
  E -->|Clear| G[Atomically merge .gitignore]
  G --> H[Write scaffold and metadata]
  H --> I[Install dependencies and initialize Git]
Loading

Reviews (1): Last reviewed commit: "test(suites): classify CLI filesystem co..." | Re-trigger Greptile

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: dd86e75bfb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 23, 2026
Merged via the queue into main with commit 5d1649f Aug 23, 2026
46 of 47 checks passed
@kojiwakayama
kojiwakayama deleted the fix/dx-usage-errors branch August 23, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants